﻿// *********************************************************************
// [DCOM Productions]
// [Copyright (C) DCOM Productions All rights reserved.]
// *********************************************************************

namespace BitFlex.Windows.Forms.CustomUI {
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing;
    using System.Data;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using System.Runtime.InteropServices;
    using System.Diagnostics;
    using System.Threading;

    /// <summary>
    /// Provides a highly customizable progress bar
    /// </summary>
    [
        ComVisible(true),
        ClassInterface(ClassInterfaceType.AutoDispatch),
        DefaultProperty("Value"),
        DefaultBindingProperty("Value"),
        ToolboxBitmap(typeof(ProgressBar))
    ]
    public partial class UIProgressBar : Control {
        /// <summary>
        /// Instantiates a new instance of the BitFlex.Windows.Forms.UIProgressBar class.
        /// </summary>
        public UIProgressBar() : base() {
            InitializeComponent();

            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            SetStyle(ControlStyles.ResizeRedraw, true);
            SetStyle(ControlStyles.UserPaint, true);

            UpdateStyles();
        }

        #region Delegates

        /// <devdoc>
        /// Used to invoke the Invalidate() method during marquee operations
        /// </devdoc>
        private delegate void InvokeInvalidateDelegate(Rectangle rect);

        #endregion

        #region Fields

        /// <devdoc>
        /// Thread-Safe thread that runs the marquee style operations.
        /// Thread that is started when the Style property is set to Marquee. 
        /// Is aborted when the Style is set from Marquee to any other style.
        /// </devdoc>
        private static Thread m_MarqueeThread;

        #endregion

        #region Events

        #region Callbacks

        /// <summary>
        /// Event handler method for the ProgressChanged event
        /// </summary>
        protected virtual void OnProgressChanged(object sender, ProgressChangedEventArgs e) {
            EventHandler<ProgressChangedEventArgs> handler = Events[EventProgressChanged] as EventHandler<ProgressChangedEventArgs>;

            if (handler != null) {
                handler(this, e);
            }
        }

        #endregion

        #region Keys

        private static readonly object EventProgressChanged = new object();

        #endregion

        /// <summary>
        /// Triggers when the Value property is changed
        /// </summary>
        public event EventHandler<ProgressChangedEventArgs> ProgressChanged {
            add {
                ProgressChanged += value;
            }
            remove {
                ProgressChanged -= value;
            }
        }

        #endregion

        #region Properties

        /// <summary>
        /// Encapsulates and hides base.BackColor
        /// </summary>
        [
            Browsable(false),
            EditorBrowsable(EditorBrowsableState.Never)
        ]
        new public Color BackColor {
            get {
                return base.BackColor;
            }
            set {
                base.BackColor = value;
            }
        }

        private float m_BackColorAngle = 90.0F;
        /// <summary>
        /// Gets or Sets the angle in which the gradient is drawn for the background
        /// </summary>
        [
            Category("Appearance"),
            DefaultValue(90.0F),
            Description("Sets the angle in which the gradient is drawn for the background")
        ]
        public float BackColorAngle {
            get {
                return m_BackColorAngle;
            }
            set {
                if (value < -360) {
                    m_BackColorAngle = -360;
                }
                else if (value > 360) {
                    m_BackColorAngle = 360;
                }
                else {
                    m_BackColorAngle = value;
                }
                Invalidate();
            }
        }

        private Color m_BackColorOne = Color.WhiteSmoke;
        /// <summary>
        /// Gets or Sets the primary background color
        /// </summary>
        [
            Category("Appearance"),
            Description("Sets the primary background color")
        ]
        public Color BackColorOne {
            get {
                return m_BackColorOne;
            }
            set {
                if (value == Color.Transparent) {
                    Debug.Assert(false, "The Transparent color is not supported.");
                }
                m_BackColorOne = value;
                Invalidate();
            }
        }

        private Color m_BackColorTwo = Color.LightGray;
        /// <summary>
        /// Gets or Sets the secondary background color
        /// </summary>
        [
            Category("Appearance"),
            Description("Sets the secondary background color")
        ]
        public Color BackColorTwo {
            get {
                return m_BackColorTwo;
            }
            set {
                if (value == Color.Transparent) {
                    Debug.Assert(false, "The Transparent color is not supported.");
                }
                m_BackColorTwo = value;
                Invalidate();
            }
        }

        private float m_BarColorAngle = 90.0F;
        /// <summary>
        /// Gets or Sets the angle of the bars gradient
        /// </summary>
        [
            Category("Appearance"),
            DefaultValue(90.0F),
            Description("Sets the angle of the bars gradient")
        ]
        public float BarColorAngle {
            get {
                return m_BarColorAngle;
            }
            set {
                if (value < -360) {
                    m_BarColorAngle = -360;
                }
                else if (value > 360) {
                    m_BarColorAngle = 360;
                }
                else {
                    m_BarColorAngle = value;
                }
                Invalidate();
            }
        }

        private Color m_BarColorOne = Color.DodgerBlue;
        /// <summary>
        /// Gets or Sets the primary color of the progress bar
        /// </summary>
        [
            Category("Appearance"),
            Description("Sets the primary color of the progress bar")
        ]
        public Color BarColorOne {
            get {
                return m_BarColorOne;
            }
            set {
                m_BarColorOne = value;
                Invalidate();
            }
        }

        private Color m_BarColorTwo = Color.RoyalBlue;
        /// <summary>
        /// Gets or Sets the secondary color of the progress bar
        /// </summary>
        [
            Category("Appearance"),
            Description("Sets the secondary color of the progress bar")
        ]
        public Color BarColorTwo {
            get {
                return m_BarColorTwo;
            }
            set {
                m_BarColorTwo = value;
                Invalidate();
            }
        }

        private int m_BarPadding = 1;
        /// <summary>
        /// Gets or Sets the inner padding of the progress bar
        /// </summary>
        [
            Category("Appearance"),
            DefaultValue(1),
            Description("Sets the inner padding of the progress bar")
        ]
        public int BarPadding {
            get {
                return m_BarPadding;
            }
            set {
                if (value < 0) {
                    m_BarPadding = 0;
                }
                else {
                    m_BarPadding = value;
                }
                Invalidate();
            }
        }

        private int m_BlockSpacing = 1;
        /// <summary>
        /// Gets or Sets the distance between each block in the Blocks style
        /// </summary>
        [
            Category("Appearance"),
            DefaultValue(1),
            Description("Sets the distance between each block in the Blocks style")
        ]
        public int BlockSpacing {
            get {
                return m_BlockSpacing;
            }
            set {
                if (value < 1) {
                    m_BlockSpacing = 1;
                }
                else {
                    m_BlockSpacing = value;
                }
                Invalidate();
            }
        }

        private int m_BlockWidth = 10;
        /// <summary>
        /// Gets or Sets the width of each block used for the Blocks style
        /// </summary>
        [
            Category("Appearance"),
            DefaultValue(10),
            Description("Sets the width of each block used for the Blocks style")
        ]
        public int BlockWidth {
            get {
                return m_BlockWidth;
            }
            set {
                if (value < 1) {
                    m_BlockWidth = 1;
                }
                else {
                    m_BlockWidth = value;
                }
                Invalidate();
            }
        }

        private Color m_BorderColor = Color.Silver;
        /// <summary>
        /// Gets or Sets the color of the border
        /// </summary>
        [
            Category("Appearance"),
            Description("Sets the color of the border")
        ]
        public Color BorderColor {
            get {
                return m_BorderColor;
            }
            set {
                if (value == Color.Transparent) {
                    Debug.Assert(false, "The Transparent color is not supported.");
                }
                m_BorderColor = value;
                Invalidate();
            }
        }

        private UIProgressBarBorderStyles m_BorderStyle = UIProgressBarBorderStyles.Standard;
        /// <summary>
        /// Gets or Sets the border style
        /// </summary>
        [
            Category("Appearance"),
            DefaultValue(typeof(UIProgressBarBorderStyles), "Standard"),
            Description("Sets the border style")
        ]
        public UIProgressBarBorderStyles BorderStyle {
            get {
                return m_BorderStyle;
            }
            set {
                m_BorderStyle = value;
                Invalidate();
            }
        }

        private bool m_EnableStatusText = false;
        /// <summary>
        /// Gets or Sets whether the status text will be drawn on the progress bar
        /// </summary>
        [
            Category("Behavior"),
            DefaultValue(false),
            Description("Gets or Sets whether the status text will be drawn on the progress bar")
        ]
        public bool EnableStatusText {
            get {
                return m_EnableStatusText;
            }
            set {
                m_EnableStatusText = value;
                Invalidate();
            }
        }

        /// <summary>
        /// Gets or Sets the base font
        /// </summary>
        [
            Browsable(false),
            EditorBrowsable(EditorBrowsableState.Never)
        ]
        new public Font Font {
            get {
                return base.Font;
            }
            set {
                base.Font = value;
            }
        }

        /// <summary>
        /// Gets or Sets the base foreground color
        /// </summary>
        [
            Browsable(false),
            EditorBrowsable(EditorBrowsableState.Never)
        ]
        new public Color ForeColor {
            get {
                return base.ForeColor;
            }
            set {
                base.ForeColor = value;
            }
        }

        private bool m_GlassOverlay = false;
        /// <summary>
        /// Gets or Sets whether the glass overlay should be drawn on the progress bar
        /// </summary>
        [
            Category("Appearance"),
            DefaultValue(false),
            Description("Sets whether the glass overlay should be drawn on the progress bar")
        ]
        public bool GlassOverlay {
            get {
                return m_GlassOverlay;
            }
            set {
                m_GlassOverlay = value;
                Invalidate();
            }
        }

        private int m_GlassOverlayAlpha = 35;
        /// <summary>
        /// Gets or Sets the alpha component of the glass overlay on the progress bar
        /// </summary>
        [
            Category("Appearance"),
            DefaultValue(35),
            Description("Sets the alpha component of the glass overlay on the progress bar")
        ]
        public int GlassOverlayAlpha {
            get {
                return m_GlassOverlayAlpha;
            }
            set {
                if (value > 255) {
                    m_GlassOverlayAlpha = 255;
                }
                else if (value < 0) {
                    m_GlassOverlayAlpha = 0;
                }
                else {
                    m_GlassOverlayAlpha = value;
                }
                Invalidate();
            }
        }

        private bool m_GlassOverlaySpring = false;
        /// <summary>
        /// Gets or Sets whether the glass overlay springs the width of the progress bar
        /// </summary>
        [
            Category("Appearance"),
            DefaultValue(false),
            Description("Gets or Sets whether the glass overlay springs the width of the progress bar")
        ]
        public bool GlassOverlaySpring {
            get {
                return m_GlassOverlaySpring;
            }
            set {
                m_GlassOverlaySpring = value;
                Invalidate();
            }
        }

        private int m_MarqueeModifier = 7;
        /// <summary>
        /// Gets or Sets the marquee speed modifier
        /// </summary>
        [
            Category("Behavior"),
            DefaultValue(7),
            Description("Gets or Sets the marquee speed modifier")
        ]
        public int MarqueeModifier {
            get {
                return m_MarqueeModifier;
            }
            set {
                m_MarqueeModifier = value;
            }
        }
        
        private static Rectangle m_MarqueeRectangle;
        /// <summary>
        /// Thread-Safe rectangle used to define the area where the marquee lies.
        /// This field is updated each time the marquee rectangle is invalidated.
        /// This allows the OnPaint method to know which portion to paint.
        /// </summary>
        public Rectangle MarqueeRectangle {
            get {
                return m_MarqueeRectangle;
            }
        }

        private UIProgressBarMarqueeStyles m_MarqueeStyle = UIProgressBarMarqueeStyles.Smooth;
        /// <summary>
        /// Gets or Sets the marquee style of the progress bar
        /// </summary>
        [
            Category("Appearance"),
            DefaultValue(typeof(UIProgressBarMarqueeStyles), "Smooth"),
            Description("Gets or Sets the marquee style of the progress bar")
        ]
        public UIProgressBarMarqueeStyles MarqueeStyle {
            get {
                return m_MarqueeStyle;
            }
            set {
                m_MarqueeStyle = value;
            }
        }

        private int m_MarqueeWidth = 45;
        /// <summary>
        /// Gets or Sets the width of the marquee
        /// </summary>
        [
            Category("Appearance"),
            DefaultValue(45),
            Description("Gets or Sets the width of the marquee")
        ]
        public int MarqueeWidth {
            get {
                return m_MarqueeWidth;
            }
            set {
                if (value < 1) {
                    m_MarqueeWidth = 1;
                }
                else if (value > (Width - (m_BarPadding * 2)) / 2) {
                    m_MarqueeWidth = (Width - (m_BarPadding * 2)) / 2;
                }
                else {
                    m_MarqueeWidth = value;
                }
            }
        }

        private int m_Maximum = 100;
        /// <summary>
        /// Gets or Sets the maximum value of the progress bar
        /// </summary>
        [
            Category("Appearance"),
            DefaultValue(100),
            Description("Sets the maximum value of the progress bar")
        ]
        public int Maximum {
            get {
                return m_Maximum;
            }
            set {
                if (value < 1) {
                    m_Maximum = 1;
                }

                if (value < m_Minimum) {
                    m_Minimum = value;
                }

                m_Maximum = value;

                if (m_Value > m_Maximum) {
                    m_Value = m_Maximum;
                }

                Invalidate();
            }
        }

        private int m_Minimum = 0;
        /// <summary>
        /// Gets or Sets the minimum value of the progress bar
        /// </summary>
        [
            Category("Appearance"),
            DefaultValue(0),
            Description("Sets the minimum value of the progress bar")
        ]
        public int Minimum {
            get {
                return m_Minimum;
            }
            set {
                if (value < 0) {
                    m_Minimum = 0;
                }

                if (value > m_Maximum) {
                    m_Minimum = value;
                }

                if (value < m_Minimum) {
                    m_Value = m_Minimum;
                }

                Invalidate();
            }
        }

        private string m_StatusText = "0%";
        /// <summary>
        /// Gets or Sets the a status description of the current progress
        /// </summary>
        [
            Category("Appearance"),
            DefaultValue("0%"),
            Description("Sets the status description of the current progress")
        ]
        public string StatusText {
            get {
                return m_StatusText;
            }
            set {
                m_StatusText = value;
                Invalidate();
            }
        }

        private HorizontalAlignment m_StatusAlignment = HorizontalAlignment.Right;
        /// <summary>
        /// Gets or Sets the alignment of the status text
        /// </summary>
        [
            Category("Appearance"),
            DefaultValue(typeof(HorizontalAlignment), "Right"),
            Description("Sets the status alignment of the status text")
        ]
        public HorizontalAlignment StatusAlignment {
            get {
                return m_StatusAlignment;
            }
            set {
                m_StatusAlignment = value;
                Invalidate();
            }
        }

        private Color m_StatusTextColor = Color.Black;
        /// <summary>
        /// Gets or Sets the color of the status text
        /// </summary>
        [
            Category("Appearance"),
            Description("Sets the color of the status text")
        ]
        public Color StatusTextColor {
            get {
                return m_StatusTextColor;
            }
            set {
                m_StatusTextColor = value;
                Invalidate();
            }
        }

        private Font m_StatusFont = new Font("Tahoma", 11, GraphicsUnit.Pixel);
        /// <summary>
        /// Gets or Sets the status text font
        /// </summary>
        [
            Category("Appearance"),
            DefaultValue(typeof(Font), "Tahoma, 11px"),
            Description("Sets the status text font")
        ]
        public Font StatusFont {
            get {
                return m_StatusFont;
            }
            set {
                m_StatusFont = value;
                Invalidate();
            }
        }

        private int m_Step = 10;
        /// <summary>
        /// Gets or Sets the amount the progress bar will step when the PerformStep() method is called
        /// </summary>
        [Category("Behavior")]
        [DefaultValue(10)]
        [Description("Gets or Sets the amount the progress bar will step when the PerformStep() method is called")]
        public int Step {
            get {
                return m_Step;
            }
            set {
                m_Step = value;
            }
        }

        private UIProgressBarStyles m_Style = UIProgressBarStyles.Smooth;
        /// <summary>
        /// Gets or Sets the style of the progress bar
        /// </summary>
        [
            Category("Appearance"),
            DefaultValue(typeof(UIProgressBarStyles), "Smooth"),
            Description("Sets the style of the progress bar")
        ]
        public UIProgressBarStyles Style {
            get {
                return m_Style;
            }
            set {
                m_Style = value;

                if (m_Style == UIProgressBarStyles.Marquee) {
                    if (m_MarqueeThread == null) {
                        m_MarqueeThread = new Thread(new ThreadStart(RunMarqueeStyle));
                        m_MarqueeThread.IsBackground = true;
                        m_MarqueeThread.Start();
                    }
                }
                else {
                    if (m_MarqueeThread != null && m_MarqueeThread.IsAlive) {
                        try {
                            m_MarqueeThread.Abort();
                        }
                        catch (System.Threading.ThreadAbortException) {
                            // Thread-Safety
                        }

                        m_MarqueeThread = null;
                    }
                    Invalidate();
                }
            }
        }

        /// <summary>
        /// Gets or Sets the base text
        /// </summary>
        [
            Browsable(false),
            EditorBrowsable(EditorBrowsableState.Never)
        ]
        new public string Text {
            get {
                return base.Text;
            }
            set {
                base.Text = value;
            }
        }

        private bool m_UseSystemRenderer = false;
        /// <summary>
        /// Gets or Sets whether the system progress bar renderer will be used.
        /// </summary>
        [
            Category("Appearance"),
            DefaultValue(false),
            Description("Gets or Sets whether the system progress bar renderer will be used.")
        ]
        public bool UseSystemRenderer {
            get {
                return m_UseSystemRenderer;
            }
            set {
                if (!ProgressBarRenderer.IsSupported) {
                    if (DesignMode) {
                        MessageBox.Show("The Visual Style ProgressBarRenderer cannot be used if the system does not support Visual Styles, or if Visual Styles are not enabled.",
                            "Design Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
                    }
                    m_UseSystemRenderer = false;
                    return;
                }
                m_UseSystemRenderer = value;
                Invalidate();
            }
        }

        private bool m_UseSystemVertical = false;
        /// <summary>
        /// Gets or Sets whether the progress bar will be drawn vertically
        /// </summary>
        [
            Category("Appearance"),
            DefaultValue(false),
            Description("Gets or Sets whether the progress bar will be drawn vertically")
        ]
        public bool UseSystemVertical {
            get {
                return m_UseSystemVertical;
            }
            set {
                m_UseSystemVertical = value;
                Invalidate();
            }
        }

        private int m_Value = 0;
        /// <summary>
        /// Gets or Sets the value of the progress bar
        /// </summary>
        [
            Category("Data"),
            DefaultValue(0),
            Description("Sets the value of the progress bar")
        ]
        public int Value {
            get {
                return m_Value;
            }
            set {
                int oldValue = m_Value;

                if (value < m_Minimum) {
                    m_Value = m_Minimum;
                }
                else if (value > m_Maximum) {
                    value = m_Maximum;
                }
                else {
                    m_Value = value;
                }

                Rectangle newValueRect = ClientRectangle;
                Rectangle oldValueRect = ClientRectangle;
                float percent = (float)(m_Value - m_Minimum) / (float)(m_Maximum - m_Minimum);
                newValueRect.Width = (int)((float)newValueRect.Width * percent);
                percent = (float)(oldValue - m_Minimum) / (float)(m_Maximum - m_Minimum);
                oldValueRect.Width = (int)((float)oldValueRect.Width * percent);
                Rectangle updateRect = new Rectangle();
                
                if (newValueRect.Width > oldValueRect.Width) {
                    updateRect.X = oldValueRect.Size.Width;
                    updateRect.Width = newValueRect.Width - oldValueRect.Width;
                }
                else {
                    updateRect.X = newValueRect.Size.Width;
                    updateRect.Width = oldValueRect.Width - newValueRect.Width;
                }

                updateRect.Height = Height;

                if (oldValue > value) {
                    Invalidate();
                }
                else {
                    if (m_Style == UIProgressBarStyles.Blocks) {
                        Invalidate();
                    }
                    else {
                        Invalidate();
                    }
                }

                OnProgressChanged(this, null);
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Increments the progress bar by 1
        /// </summary>
        public void Increment() {
            Value = Value + 1;
        }

        private void InvokeInvalidate(Rectangle rect) {
            m_MarqueeRectangle = rect;
            Invalidate();
        }

        /// <summary>
        /// Increments the progress bar by the amount specified by the Step property
        /// </summary>
        public void PerformStep() {
            Value = Value + Step;
        }

        private void RunMarqueeStyle() {
            Rectangle rect = new Rectangle();
            rect.X = -m_MarqueeWidth;
            rect.Y = 1 + m_BarPadding;
            rect.Height = (Height - 2) - (m_BarPadding * 2);
            rect.Width = m_MarqueeWidth;

            InvokeInvalidateDelegate invalidateDelegate = new InvokeInvalidateDelegate(InvokeInvalidate);

            while (m_Style == UIProgressBarStyles.Marquee) {
                rect.Y = 1 + m_BarPadding;
                rect.Height = (Height - 2) - (m_BarPadding * 2);
                rect.Width = m_MarqueeWidth;

                Thread.Sleep(m_MarqueeModifier);
                Invoke(invalidateDelegate, rect);

                if (rect.X < Width) {
                    rect.X += 1;
                }
                else {
                    rect.X = 1 + (int)m_BarPadding - rect.Width;
                }
            }
        }

        #endregion

        #region Overrides
        
        protected override void OnPaint(PaintEventArgs e) {
            UIProgressBarRenderer renderer = new UIProgressBarRenderer(this, e.Graphics);
            renderer.DrawBackground();
            renderer.DrawProgress();
            renderer.DrawBorder();
        }

        #endregion
    }
}